﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using Microsoft.Pex.Framework;

namespace AutomaTones.Model.Automaton_Model.RuleLib {
    /// <author>Anders Øland / anoe@itu</author>
    /// <summary>
    /// Provides the rule used for the Wolfram Automatons - or any other pattern-based rule-type cellular automaton.
    /// <br></br><br></br>
    /// This class also facilitates easy setup of Wolfram/pattern-based CA. 
    /// </summary>
    [PexClass]
    public static partial class WolframAutomata {
        /// <summary>
        /// This rule is intended for use with Wolfram one-dimensional automata -
        /// or any other pattern-based rule-type cellular automaton.
        /// The state space may have any size - but MUST be shared by all cells, and the lower bound MUST be zero - 
        /// and the grid can be N-dimensional.
        /// <br></br><br></br>
        /// The rule is represented by a Wolfram code; 
        /// range 0 to S^S^(2n+1) - 1 where S = size of the state space, n = size of the neighbourhood.
        /// Eg. a Wolfram Code for a binary rule based on two neighbours:<br></br>
        /// Pattern 1: 00 --> new state 0<br></br>
        /// Pattern 2: 01 --> new state 1<br></br>
        /// Pattern 3: 10 --> new state 0<br></br>
        /// Pattern 4: 11 --> new state 1<br></br>
        /// The code number is the binary number formed by the state column: %1010
        /// ==> Rule number 10 (decimal). <br></br><br></br>
        /// Thus when eg. pattern 4 is parsed to this rule ==> it returns bit 4 of the Wolfram code.<br></br><br></br>
        /// NOTE: 8 neighbours and 8 possible states ==> 8^8^17 - 1 possible rules(!!!) and ulong.MaxValue = 2^64 -1. 
        /// Thus, not all possible rules can be used!
        /// </summary>
        /// <param name="cell"></param>
        /// <param name="statesOfNeighbours"></param>
        /// <param name="wolframCode">MUST be less than 2^64</param>
        /// <returns></returns>
        public static int WolframRule(Cell cell, ref IEnumerable<Rule.StateAtCoordinate> statesOfNeighbours,
                                      int[] wolframCode) {
            Contract.Requires(null != cell);
            Contract.Requires(0 == cell.State.SpaceMin && 0 < cell.State.SpaceMax);
            Contract.Requires(Contract.ForAll(statesOfNeighbours, sn => 1 == sn.Position.Coord.Length));
            Contract.Requires(Contract.ForAll(statesOfNeighbours, sn => cell.State.SpaceMin <= sn.State
                                                                        && cell.State.SpaceMax >= sn.State));
            Contract.Ensures(cell.State.SpaceMin <= Contract.Result<int>());
            Contract.Ensures(cell.State.SpaceMax >= Contract.Result<int>());
            // Find the bit(or digit) number representing this pattern in the Wolfram Code for the chosen rule.
            var bit = Tools.Math.BaseToDecimal((from n in statesOfNeighbours select n.State).ToArray(),
                                               (uint) (cell.State.SpaceMax - cell.State.SpaceMin + 1));
            return wolframCode[bit];
        }

        /// <summary>
        /// Converts a Wolfram Code from decimal to any base( = the amount of possible states).
        /// The resulting array will have the length required to represent 2^64 - 1 in the given base.
        /// </summary>
        /// <param name="wolframCode">Decimal representation</param>
        /// <param name="stateSpace">The amount of possible states</param>
        /// <returns>Base[statespace] representation of the code by array of digits</returns>
        public static int[] WolframCode(ulong wolframCode, uint stateSpace = 2u) {
            Contract.Requires(1 < stateSpace);
            Contract.Ensures(Tools.Math.DecimalToBase(ulong.MaxValue, stateSpace).Length >=
                             Contract.Result<int[]>().Length);
            var digits = Tools.Math.DecimalToBase(ulong.MaxValue, stateSpace).Length;
            return Tools.Math.DecimalToBase(wolframCode, stateSpace, (uint) digits);
        }

        /// <summary>
        /// Converts a series of rule patterns into a Wolfram Code represented by an array of digits.
        /// The array will have the lenght needed to represent 2^64 -1 (ulong.MaxValue) in base[stateSpace]
        /// </summary>
        /// <param name="patterns">All the patterns included in the rule - concatenated in one array</param>
        /// <param name="states">List of the states which each pattern map to</param>
        /// <param name="numOfNeighbours">The size of the neighbourhood used by the intended rule</param>
        /// <param name="stateSpace">Amount of possible states</param>
        /// <returns>Base[statespace] representation of the code by array of digits</returns>
        public static int[] WolframCode(int[] patterns, int[] states, uint numOfNeighbours = 3u, uint stateSpace = 2u) {
            Contract.Requires(1 < stateSpace);
            Contract.Requires(0 == patterns.Length % numOfNeighbours);
            Contract.Requires(numOfNeighbours == states.Length);
            Contract.Ensures(Tools.Math.DecimalToBase(ulong.MaxValue, stateSpace).Length >=
                             Contract.Result<int[]>().Length);
            var digits = Tools.Math.DecimalToBase(ulong.MaxValue, stateSpace).Length;
            int[] code = new int[digits];
            for (int i = 0; i < patterns.Length / numOfNeighbours; i++) {
                int[] pattern = (from x in patterns
                                     .Skip(i * (int) numOfNeighbours)
                                     .Take((int) numOfNeighbours)
                                 select x).ToArray();
                var digit = (int) Tools.Math.BaseToDecimal(pattern, stateSpace);
                code[digit] = states[i];
            }
            return code;
        }

        /// <summary>
        /// Creates a Wolfram Automaton (one-dimensional)
        /// </summary>
        /// <param name="viewedSize">view-grid size</param>
        /// <param name="ruleNumber">The rule number (wolfram code) to be used: 0-255</param>
        /// <param name="stateSpace">size of the state space, 2 = binary</param>
        /// <returns></returns>
        public static CellularAutomaton NewWolframAutomaton(uint viewedSize, uint ruleNumber, uint stateSpace = 2u) {
            Contract.Requires(3 <= viewedSize);
            Contract.Requires(2 <= stateSpace);
            Contract.Requires(256 > ruleNumber);
            Rule wolframRule = NewWolframRule(WolframCode(ruleNumber));
            return new WolframAutomaton(wolframRule, (int) stateSpace, viewedSize);
        }

        /// <summary>
        /// Creates a Wolfram Automaton (one-dimensional) instance, based on the given patterns and corresponding states.
        /// </summary>
        /// <param name="viewedSize">view-grid size</param>
        /// <param name="patterns">the patterns of the rule concatenated into one array</param>
        /// <param name="states">the states correlating to each of the patterns</param>
        /// <param name="numOfNeighbours">size of the neighbourhood</param>
        /// <param name="stateSpace">size of the state space, 2 = binary</param>
        /// <returns></returns>
        public static CellularAutomaton NewWolframAutomaton(uint viewedSize, int[] patterns, int[] states, uint numOfNeighbours = 3u,
                                                            uint stateSpace = 2u) {
            Contract.Requires(3 <= viewedSize);
            Contract.Requires(1 < stateSpace);
            var wolframCode = WolframCode(patterns, states, numOfNeighbours, stateSpace);
            Rule wolframRule = NewWolframRule(wolframCode);
            return new WolframAutomaton(wolframRule, (int) stateSpace, viewedSize);
        }

        /// <summary>
        /// Get a new pattern-rule-based automaton instance, based on the given patterns and corresponding states, 
        /// for a two-dimensional CA
        /// </summary>
        /// <param name="size"></param>
        /// <param name="patterns">the patterns of the rule concatenated into one array</param>
        /// <param name="states">the states correlating to each of the patterns</param>
        /// <param name="numOfNeighbours">size of the neighbourhood</param>
        /// <param name="stateSpace"></param>
        /// <returns></returns>
        public static CellularAutomaton NewPatternAutomaton(uint size, int[] patterns, int[] states, uint numOfNeighbours = 3u,
                                                            uint stateSpace = 2u) {
            Contract.Requires(3 <= size);
            Contract.Requires(1 < stateSpace);
            var wolframCode = WolframCode(patterns, states, numOfNeighbours, stateSpace);
            Rule wolframRule = NewWolframRule(wolframCode);
            return new CellularAutomaton(1, size, wolframRule);
        }

        /// <summary>
        /// Get a new Wolfram Rule instance (for one-dimensional use).
        /// </summary>
        /// <param name="wolframCode"></param>
        /// <param name="stateSpace">MUST be >= 2</param>
        /// <returns>Wolfram CA</returns>
        public static Rule NewWolframRule(int[] wolframCode, uint stateSpace = 2u) {
            Contract.Requires(null != wolframCode);
            Contract.Requires(1 < stateSpace);
            WolframRule wolframRule = new WolframRule(1, WolframRule, wolframCode);

            // Setup the coordinates for the neighbours which the rule uses 
            var neighbourhood = new[] {-1, 0, 1};
            foreach (int n in neighbourhood) wolframRule.AddNeighbour(n);
            return wolframRule;
        }

        /// <summary>
        /// Get a new WolframRule instance - for two-dimensional pattern-based CA).
        /// </summary>
        /// <param name="wolframCode"></param>
        /// <param name="stateSpace">MUST be >= 2</param>
        /// <returns>Wolfram CA</returns>
        [PexMethod]
        public static Rule NewPatternRule(int[] wolframCode, uint stateSpace = 2u) {
            Contract.Requires(null != wolframCode);
            Contract.Requires(1 < stateSpace);
            WolframRule wolframRule = new WolframRule(2, WolframRule, wolframCode);

            // Setup the coordinates for the neighbours which the rule uses 
            var neighbourhood = new[] {
                                          new[] {-1, 1}, new[] {0, 1}, new[] {1, 1}, new[] {1, 0},
                                          new[] {1, -1}, new[] {0, -1}, new[] {-1, -1}, new[] {-1, 0}
                                      };
            foreach (var n in neighbourhood) wolframRule.AddNeighbour(n);
            return wolframRule;
        }

        /// <summary>
        /// Demo game Wolfram automaton in the console.
        /// Press a key + return to evolve.
        /// </summary>
        [PexMethod]
        public static void WolframDemo(uint rule, int generations) {
            CellularAutomaton wolframAutomaton = NewWolframAutomaton(70, rule);
//            wolframAutomaton[35] = 1;
            Console.WriteLine("Wolfram elementary automaton using Rule "+rule);
            while (generations-- > 0) {
                Console.Write(wolframAutomaton);
                wolframAutomaton.Evolve();
            }
                Console.Read();
        }
    }
}